﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web.Mvc;
using System.Linq;

using Lilium.Control;
using Lilium.Web.Mvc.Context;
using Lilium.Web.Mvc;
using Lilium.Web.Mvc.State;
using System.Linq.Expressions;

namespace Lilium.Web.Mvc
{
    [ActionContext(Order = -1)]
    public class EntityController : Controller, Lilium.IServiceProvider, Lilium.Web.Mvc.IController
    {
        public EntityController(Lilium.IServiceProvider serviceProvider)
        {
            if (serviceProvider == null) throw new ArgumentNullException("serviceProvider");

            m_UnderlyingServiceProvider = serviceProvider;
        }

        public EntityController(Lilium.IServiceProvider serviceProvider, Type entityType)
            : this(serviceProvider)
        {
            m_EntityType = entityType;
        }

        public ActionResult CreateView(string viewName, string masterName, object model)
        {
            return View(viewName, masterName, model);
        }

        public new bool TryUpdateModel<TModel>(TModel model, string[] includeProperties)
            where TModel : class
        {
            return base.TryUpdateModel(model, includeProperties);
        }

        #region public Type EntityType

        private readonly Type m_EntityType;

        public Type EntityType
        {
            get
            {
                return m_EntityType;
            }
        }

        #endregion

        #region Lilium.IServiceProvider Members

        public TService GetService<TService>()
        {
            var service = this.UnderlyingServiceProvider.GetService<TService>();
            InitializeService(service);
            return service;
        }

        #endregion

        #region IServiceProvider Members

        public object GetService(Type serviceType)
        {
            var service = this.UnderlyingServiceProvider.GetService(serviceType);
            InitializeService(service);
            return service;
        }

        #endregion

        #region Common View Helpres

        public ViewResult EntityView<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
        {
            var repository = this.GetRepository<TEntity>();
            var model = this.ApplyFilters(repository).Where(predicate).Single(); //TODO: обрабатывать ошибки если сущность не найдена или их несколько.
            return View(model);
        }

        /// <summary>
        /// Load entities of specified type, apply all filters and create <see cref="ViewResult"/> with resulting set.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <returns></returns>
        public ActionResult TableView<TEntity>() where TEntity : class
        {
            var repository = this.GetRepository<TEntity>();
            var model = this.ApplyFilters(repository);
            return ExecuteWorkflow(wf => from _ in wf.SetModel(model) select wf); // используется workflow чтобы загрузить информацию об ошибках, сохранить состояние и т.п. конечно отказываься надо от этой фигни.
        }
        /*
        public ViewResult TableView<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class
        {
            var repository = this.GetRepository<TEntity>();
            var model = this.ApplyFilters(repository).Where(filter);
            return View(model);
        }
        */
        public ActionResult TableView<TEntity>(Func<IQueryable<TEntity>, IQueryable<TEntity>> selector) where TEntity : class
        {
            if (selector == null) throw new ArgumentNullException("selector");

            var repository = this.GetRepository<TEntity>();
            var model = selector(this.ApplyFilters(repository));
            return ExecuteWorkflow(wf => from m in wf.SetModel(model) select wf); // wf используется для отображения сообщений об ошибках. Хорошо бы от него избавиться.
        }

        #endregion

        protected override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            base.OnResultExecuted(filterContext);

            foreach (var controllerService in ControllerServices)
                controllerService.OnResultExecuted();
        }

        protected ActionResult ExecuteWorkflow(Func<EntityWorkflow, Either<ActionResult, EntityWorkflow>> executeFunc)
        {
            return ExecuteWorkflow<EntityWorkflow>(executeFunc);
        }

        protected ActionResult ExecuteWorkflow<TWorkflow>(Func<TWorkflow, Either<ActionResult, TWorkflow>> executeFunc) where TWorkflow : EntityWorkflow
        {
            if (executeFunc == null) throw new ArgumentNullException("executeFunc");

            var workflow = this.GetService<IEntityWorkflowFactory>(true).GetWorkflow<TWorkflow>(this);

            var result = executeFunc(workflow);

            switch (result.Kind)
            {
                case EitherKind.Left:
                    return result.Left;
                case EitherKind.Right:
                    return result.Right.GetActionResult();
                default:
                    throw new InvalidOperationException(
                        string.Format("Non-exhaustive patterns in function {0}: unable to match Ether's kind of {1}",
                        MethodBase.GetCurrentMethod().Name,
                        result.Kind));
            }
        }

        private void InitializeService(object service)
        {
            var controllerService = service as IControllerService;
            if (controllerService != null)
                ControllerServices.Add(controllerService);

            var statefullObject = service as IStatefulObject;
            if (statefullObject != null)
            {
                var stateService = this.GetService<IStateService>(true);
                stateService.RegisterConsumer(statefullObject, true);
            }
        }

        #region private HashSet<IControllerService> ControllerServices

        private readonly HashSet<IControllerService> m_ControllerServices = new HashSet<IControllerService>();

        private HashSet<IControllerService> ControllerServices
        {
            get
            {
                return m_ControllerServices;
            }
        }

        #endregion

        #region private IServiceProvider UnderlyingServiceProvider

        private readonly IServiceProvider m_UnderlyingServiceProvider;

        private IServiceProvider UnderlyingServiceProvider
        {
            get
            {
                return m_UnderlyingServiceProvider;
            }
        }

        #endregion
    }
}